﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Aleator.Core.Techniques
{
    /// <summary>
    /// Rocking singleton
    /// </summary>
    public sealed class Rock : Technique
    {
         #region Members

        private static readonly Rock m_instance = new Rock();
        private static readonly int m_drumsOn = Convert.ToInt32(Aleator.Resources.AppResources.BigKitOnMessageID);
        private static readonly int m_drumsOff = Convert.ToInt32(Aleator.Resources.AppResources.BigKitOffMessageID);

        #endregion

        #region Constructor

        private Rock() { }

        #endregion

        #region Public Methods

        /// <summary>
        /// BounceGenerator instance
        /// </summary>
        public new static Rock Instance
        {
            get
            {
                return m_instance;
            }
        }

        public override void  Generate(ConcretePhrase p)
        {
            m_overrideNoteOn = 0;
            m_overrideNoteOff = 0;

            if (p.GetType() != typeof(DrumPhrase))
                throw new Exception("Rocki technique only implemented for drums");

            base.Generate(p);

            DrumPhrase dp = (DrumPhrase)p;
            dp.TechniqueType = TechniqueType.Rock;

            //Calculate totals
            SetTotals(dp);

            //Figure out remainders for all lists
            if (dp.KickNotes.Length > dp.Progression.TotalMeasures)
                dp.KickMod = dp.KickNotes.Length % dp.Progression.Numerator;

            if (dp.SnareNotes.Length > dp.Progression.TotalMeasures)
                dp.SnareMod = dp.SnareNotes.Length % dp.Progression.Numerator;

            if (dp.HiHatNotes.Length > dp.Progression.TotalMeasures)
                dp.HighHatMod = dp.HiHatNotes.Length % dp.Progression.Numerator;

            BuildKick(dp);
            BuildSnare(dp);
            BuildHiHat(dp);
            //TODO: Implement crash.
        }

        #endregion

        #region Private Methods

        private static void BuildKick(DrumPhrase dp)
        {
            ConcreteNote tmpNote;
            dp.KickResolution = 4;

            int?[] primaries = { 0, 8, 16, 24 };
            int?[] secondaries = { 4, 12, 20 };
            int?[] tertiaries = { 2, 6, 10, 14, 18, 22, 26 };

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int kicksInMeasure = dp.KickNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.KickResolution;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            Debug.WriteLine("Filling initial kick measure. KicksInMeasure: " + kicksInMeasure);

            //Fill out the first measure of the phrase
            while (j < kicksInMeasure)
            {
                Debug.WriteLine("Attemptimg to locate kick note " + (j + 1));
                m_instance.GetKick(dp.Progression.Numerator, j, primaries, secondaries, tertiaries);
                Debug.WriteLine("Kick note " + (j + 1) + " located");

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == 0).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    Debug.WriteLine("Adding kick note " + (j + 1));
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    Debug.WriteLine("Kick note " + (j + 1) + " added.");
                    j++;
                }
            }

            Debug.WriteLine("Filling subsequent kick measures");

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * kicksInMeasure)
                    break;

                for (int k = 0; k < kicksInMeasure; k++)
                {
                    tmpNote = dp.KickNotes[j % kicksInMeasure];
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            Debug.WriteLine("Locating kick remainders");

            //TODO: insure that kick remainders are only added on primary or secondary locations
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.KickNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.KickResolution; i++)
                {
                    if (!fullnotes.Where(n => n.Location == i).Any() && (primaries.Contains(i) || secondaries.Contains(i)))
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.KickNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.KickNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildSnare(DrumPhrase dp)
        {
            ConcreteNote tmpNote;
            dp.SnareResolution = 4;

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int snaresInMeasure = dp.SnareNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.SnareResolution;

            //Decide if we are hitting the head or side of the snare
            int notenumber;

            int?[] secondaries = { 4, 12, 20 };
            int?[] tertiaries = { 2, 6, 10, 14, 18, 22, 26 };

            if (Session.RndGenerator.Next(5) <= 3)
                notenumber = DrumPhrase.SnareNum;
            else
                notenumber = DrumPhrase.SideStickNum;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < snaresInMeasure)
            {
                m_instance.GetSnare(dp.Progression.Numerator, j, secondaries, tertiaries);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.SnareNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, notenumber, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * snaresInMeasure)
                    break;

                for (int k = 0; k < snaresInMeasure; k++)
                {
                    tmpNote = dp.SnareNotes[j % snaresInMeasure];
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, notenumber, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.SnareNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.SnareResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.SnareNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.SnareNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, notenumber, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.SnareNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildHiHat(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            //Quantize the snare to 16ths (quarters / 4)
            dp.HiHatResolution = 4;

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int highHatsInMeasure = dp.HiHatNotes.Length / dp.Progression.TotalMeasures;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < highHatsInMeasure)
            {
                m_location = Session.RndGenerator.Next(dp.Progression.Numerator * dp.HiHatResolution);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.HiHatNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * highHatsInMeasure)
                    break;

                for (int k = 0; k < highHatsInMeasure; k++)
                {
                    tmpNote = dp.HiHatNotes[j % highHatsInMeasure];
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.HiHatNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.HiHatResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.HiHatNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.HiHatNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.HiHatNotes[j]);
                        j++;
                    }
                }
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Set note totals
        /// </summary>
        /// <param name="p">Phrase we are filling</param>
        protected override void SetTotals(Phrase p)
        {
            DrumPhrase dp = (DrumPhrase)p;

            int kicks = 0;
            int snares = 0;
            int highhats = 0;

            //Generate drum totals - make Mu proportional to the amount of quarter notes available in the progression as
            //long as its not type 3. Otherewise Mu should be propertional to half notes.
            if (dp.PhraseRhythm.RhythmType == 1)
            {
                kicks = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * .375);
                snares = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * .25);
                highhats = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * 1.5);
            }
            else if (dp.PhraseRhythm.RhythmType == 3)
            {
                if (dp.Progression.Numerator % 2 != 0)
                    throw new NotImplementedException("Sparse rhythms in odd time signatures are not implemented.");

                kicks = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * .25);
                snares = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * .125);
                highhats = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * .75);
            }
            else
                throw new NotImplementedException("Rock technique only implemented for driving and sparse rhythm types!");

            dp.KickNotes = new ConcreteNote[kicks];
            dp.SnareNotes = new ConcreteNote[snares];
            dp.HiHatNotes = new ConcreteNote[highhats];

            dp.Notes = new List<ConcreteNote>();
        }

        private void GetKick(int numerator, int counter, int?[] primaries, int?[] secondaries, int?[] tertiaries)
        {
            int selector = 0;
            int loc = 0;

            int primarymax = 0;
            int secondarymax = 0;

            int? location = null;

            if (numerator == 7)
                primarymax = 4;
            else
                primarymax = numerator - 2;

            if (numerator == 4)
                secondarymax = 2;
            else
                secondarymax = 3;

            if (counter == 0)
            {
                loc = Session.RndGenerator.Next(primarymax);
                m_location = primaries[loc].Value;
                primaries[loc] = null;
            }
            else if (counter <= numerator)
            {
                selector = Session.RndGenerator.Next(2);

                if (selector <= 1 && counter < primarymax && primaries.Where(p => p.HasValue).Any())
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(primarymax);

                        if (primaries[loc].HasValue)
                            location = primaries[loc].Value;
                    }

                    m_location = location.Value;
                    primaries[loc] = null;
                }
                else
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(secondarymax);

                        if (secondaries[loc].HasValue)
                            location = secondaries[loc].Value;
                    }

                    m_location = location.Value;
                    secondaries[loc] = null;
                }
            }
            else
            {
                selector = Session.RndGenerator.Next(3);

                //Fill out primaries since we probably already have some variety
                if (primaries.Where(p => p.HasValue).Any())
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(primarymax);

                        if (primaries[loc].HasValue)
                            location = primaries[loc].Value;
                    }

                    m_location = location.Value;
                    primaries[loc] = null;
                }
                else if (selector <= 1 && secondaries.Where(s => s.HasValue).Any())
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(secondarymax);

                        if (secondaries[loc].HasValue)
                            location = secondaries[loc].Value;
                    }

                    m_location = location.Value;
                    secondaries[loc] = null;
                }
                else if (selector <= 3 )
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(secondarymax);

                        if (secondaries[loc].HasValue)
                            location = secondaries[loc].Value;
                    }

                    m_location = location.Value;
                    secondaries[loc] = null;
                }
            }
        }

        private void GetSnare(int numerator, int counter, int?[] secondaries, int?[] tertiaries)
        {
            int selector = 0;
            int loc = 0;

            int secondarymax = 0;

            int? location = null;

            if (numerator == 4)
                secondarymax = 3;
            else
                secondarymax = 4;

            if (counter == 0)
            {
                loc = Session.RndGenerator.Next(secondarymax);
                m_location = secondaries[loc].Value;
                secondaries[loc] = null;
            }
            else if (counter <= numerator)
            {
                selector = Session.RndGenerator.Next(3);

                if (selector <= 1 && secondaries.Where(s => s.HasValue).Any())
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(secondarymax);

                        if (secondaries[loc].HasValue)
                            location = secondaries[loc].Value;
                    }

                    m_location = location.Value;
                    secondaries[loc] = null;
                }
                else
                {
                    while (location == null)
                    {
                        loc = Session.RndGenerator.Next(numerator);

                        if (tertiaries[loc].HasValue)
                            location = tertiaries[loc].Value;
                    }

                    m_location = location.Value;
                    tertiaries[loc] = null;
                }
            }
        }

        #endregion

    }
}
